import {Layer} from "./Layer";
import {FillExtrusionStyle, FillExtrusionStyleConfig} from "./style/FillExtrusionStyle";
import {Filter} from "./filter/Filter";
import {LatLng} from "../geom/LatLng";
import {FeatureCollection, MultiPolygon, Polygon, Position} from "@turf/helpers";
import {
    BufferGeometry,
    Color, EdgesGeometry, ExtrudeBufferGeometry, ExtrudeGeometry, LineBasicMaterial, LineSegments, Mesh,
    MeshBasicMaterial, Shape,
    ShapeBufferGeometry, ShapeGeometry,
    Vector2
} from "three";
import {FillStyleConfig} from "./style/FillStyle";
import {Object3D} from "three/three-core";

export class FillExtrusionLayer extends Layer {

    protected fillMaterial: MeshBasicMaterial;

    protected outLineMaterial: LineBasicMaterial;

    protected extrudeSettings: any;

    public style: FillExtrusionStyle;

    public filter: Filter;

    constructor(layerId: string, sourceId: string, filter?: Filter) {
        super(layerId, sourceId);

        this.style = new FillExtrusionStyle();
        this.refreshStyle();
        this.filter = filter;
    }

    public setStyle(config: FillExtrusionStyleConfig) {
        if (config.fillExtrusionColor) {
            this.style.fillExtrusionColor = new Color(config.fillExtrusionColor as any)
                .convertLinearToGamma();
        }
        if (config.fillExtrusionOpacity) {
            this.style.fillExtrusionOpacity = config.fillExtrusionOpacity;
        }
        if (config.fillExtrusionOutlineColor) {
            this.style.fillExtrusionOutlineColor = new Color(config.fillExtrusionOutlineColor as any)
                .convertLinearToGamma();
        }
        if (config.fillExtrusionOutlineWidth) {
            this.style.fillExtrusionOutlineWidth = config.fillExtrusionOutlineWidth;
        }
        this.refreshStyle();
    }

    protected refreshStyle() {
        this.fillMaterial = new MeshBasicMaterial({
            color: this.style.fillExtrusionColor,
            opacity: this.style.fillExtrusionOpacity,
            transparent: true
        });
        this.outLineMaterial = new LineBasicMaterial({
            color: this.style.fillExtrusionOutlineColor,
            linewidth: this.style.fillExtrusionOutlineWidth,
            transparent: false
        });
        this.extrudeSettings = {
            //高度
            depth: this.style.extrusionHeight,
            bevelEnabled: false
        };
    }

    onSourceDataChange(features: FeatureCollection): void {
        let threeGeomArray: Array<ExtrudeGeometry> = [];

        features.features
            .filter(feature => {
                return this.filter ? this.filter.filter(feature) : true;
            })
            .forEach(feature => {
                if (feature.geometry.type == "Polygon") {
                    let polygon = feature.geometry as Polygon;

                    let shape = this.coordinatesToShape(polygon.coordinates);

                    threeGeomArray.push(new ExtrudeGeometry(shape),this.extrudeSettings);
                } else if (feature.geometry.type == "MultiPolygon") {
                    let multiPolygon = feature.geometry as MultiPolygon;

                    let shapes: Array<Shape> = [];

                    multiPolygon.coordinates.forEach(coordinates => {
                        shapes.push(this.coordinatesToShape(coordinates))
                    });

                    threeGeomArray.push(new ExtrudeGeometry(shapes,this.extrudeSettings));
                }
            });

        let meshArray: Array<Object3D> = [];

        let rootPolygon = threeGeomArray[0];

        for (let i = 1; i < threeGeomArray.length; i++) {
            rootPolygon.merge(threeGeomArray[i]);
        }

        meshArray.push(new Mesh(new BufferGeometry().fromGeometry(rootPolygon), this.fillMaterial));

        meshArray.push(new LineSegments(new EdgesGeometry(rootPolygon, 1)
            , this.outLineMaterial));

        this.scene.addLayer(this.layerId, meshArray);
    }

    private coordinatesToShape(coordinates: Position[][]): Shape {
        let points: Vector2[] = [];

        coordinates.forEach(coordinates => {
            coordinates.forEach(position => {
                let vec = this.map.mapRenderer.camera.projection(new LatLng(position[1], position[0]));

                points.push(vec)
            })
        });

        return new Shape(points);
    }
}
